﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CalculationCore.UX
{
    public class Matrix
    {
        private List<List<float>> mMatrix;

        public Matrix()
        {
            // empty Matrix - creator
            mMatrix = new List<List<float>>();
        }

        public Matrix(float a)
        {
            this.mMatrix = new List<List<float>>();
            this.mMatrix[0].Add(a);
        }

        public Matrix(List<List<float>> mat)
        {
            // mMatrix = new List<List<float>>(mat); ovo zapravo uradi mMatrix= mat!!!!!!!!????
            mMatrix = new List<List<float>>();
            List<float> templist = new List<float>();
            for (int i = 0; i < mat.Count; i++)
            {
                templist = new List<float>();
                for (int j = 0; j < mat[0].Count; j++)
                    templist.Add(mat[i][j]);
                this.mMatrix.Add(templist);
            }

        }

        public Matrix(int n, int m)
        {
            // n x m Matrix filled with zeros - creator
            /*
            if ((n<=0) || (m<=0))
                pukni/upozori;
            */
            mMatrix = new List<List<float>>();
            List<float> templist = new List<float>();
            for (int i = 0; i < n; i++)
            {
                templist = new List<float>();
                for (int j = 0; j < m; j++)
                    templist.Add(0);
                this.mMatrix.Add(templist);
            }

        }

        public Matrix(int n, int m, float k)
        {
            // n x m Matrix filled with float k - creator
            /*
            if ((n<=0) || (m<=0))
                pukni/upozori;
            */
            mMatrix = new List<List<float>>();
            List<float> templist = new List<float>();
            for (int i = 0; i < n; i++)
            {
                templist = new List<float>();
                for (int j = 0; j < m; j++)
                    templist.Add(k);
                this.mMatrix.Add(templist);
            }

        }

        public Matrix(Matrix mat)
        {
            mMatrix = new List<List<float>>();
            List<float> templist = new List<float>();
            for (int i = 0; i < mat.mMatrix.Count; i++)
            {
                templist = new List<float>();
                for (int j = 0; j < mat.mMatrix[0].Count; j++)
                    templist.Add(mat.mMatrix[i][j]);
                this.mMatrix.Add(templist);
            }
        }

        public Matrix Eye(int n)
        {
            Matrix temp = new Matrix(n, n);
            for (int i = 0; i < n; i++)
            {
                temp[i][i] = 1;
            }
            return temp;
        }

        public static Matrix ColonOperator(int begin, int end)
        {
            // create Matrix with one row which is filled from begin to end (difference is one)
            /*
            if (begin>end)
                pukni/upozori;
            */
            Matrix temp = new Matrix(1, end - begin + 1);
            for (int i = begin; i <= end; i++)
                temp.mMatrix[0][i - begin] = i;
            return temp;
        }

        public static Matrix ColonOperator(float begin, float step, float end)
        {
            // create Matrix with one row which is filled from begin to end (difference is step)
            /*
            if (step==0)
                pukni;
            */
            int count = (int)((end - begin) / step);
            int n = count;
            if (count < 0)
                return null;
            Matrix temp = new Matrix(1, count + 1);
            while (count-- >= 0)
            {
                temp.mMatrix[0][n - count - 1] = begin;
                begin += step;
            }
            return temp;
        }

        public static Matrix operator +(Matrix mat1, Matrix mat2)
        {
            // Matrix summation operator
            /*
            if ((mat1.mMatrix.Count!=mat2.mMatrix.Count) || (mat1.mMatrix[0].Count!=mat2.mMatrix[0].Count))
                pukni;
            */
            Matrix temp = new Matrix(mat1.mMatrix.Count, mat1.mMatrix[0].Count);
            for (int i = 0; i < mat1.mMatrix.Count; i++)
                for (int j = 0; j < mat1.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = mat1.mMatrix[i][j] + mat2.mMatrix[i][j];
            return temp;
        }

        public static Matrix operator -(Matrix mat1, Matrix mat2)
        {
            // Matrix subtraction operator
            /*
            if ((mat1.mMatrix.Count!=mat2.mMatrix.Count) || (mat1.mMatrix[0].Count!=mat2.mMatrix[0].Count))
                pukni;
            */
            Matrix temp = new Matrix(mat1.mMatrix.Count, mat1.mMatrix[0].Count);
            for (int i = 0; i < mat1.mMatrix.Count; i++)
                for (int j = 0; j < mat1.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = mat1.mMatrix[i][j] - mat2.mMatrix[i][j];
            return temp;
        }

        public List<float> this[int i]
        {
            get
            {
                return this.mMatrix[i];
            }
            set
            {

            }
        }

        public Matrix MulitpScalar(float scalar)
        {
            // multiplication Matrix with scalar
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[i][j] * scalar;
            return temp;
        }

        public Matrix MultiplicateDot(Matrix mat)
        {
            // Matrix multiplication element by elemnt
            /*
            if ((this.mMatrix[0].Count!=mat.mMatrix.Count[0])||(this.mMatrix.Count!=mat.mMatrix.Count))
                pukni;
            */

            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[i][j] * mat[i][j];
            return temp;

        }

        public Matrix Multiplicate(Matrix mat)
        {
            // Matrix multiplication
            /*
            if (this.mMatrix[0].Count!=mat.mMatrix.Count)
                pukni;
            */
            Matrix temp = new Matrix(this.mMatrix.Count, mat.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < mat.mMatrix[0].Count; j++)
                    for (int k = 0; k < this.mMatrix[0].Count; k++)
                        temp.mMatrix[i][j] += this.mMatrix[i][k] * mat.mMatrix[k][j];
            return temp;
        }

        private int IndexOfAbsMaxColumn(int n, int m)
        {
            // returns index of colomn which has the biggest value in n-raw (comparision begins from m-colomn)
            /*
            if ((n<0) || (n>=this.mMatrix.Count) || (m<0) || (m>=this.mMatrix[0].Count))
                pukni;
            */
            float max = Math.Abs(this.mMatrix[n][m]);
            int index = m;
            for (int j = m + 1; j < this.mMatrix[n].Count; j++)
                if (max < Math.Abs(this.mMatrix[n][j]))
                {
                    max = Math.Abs(this.mMatrix[n][j]);
                    index = j;
                }
            return index;
        }

        private int IndexOfAbsMaxRaw(int n, int m)
        {
            float tempmax = 0;
            int tempindex = m;
            for (int i = m; i < this.mMatrix.Count; i++)
                if (Math.Abs(this[i][n]) > tempmax)
                {
                    tempmax = Math.Abs(this[i][n]);
                    tempindex = i;
                }
            return tempindex;
        }


        private float AbsMaxColumn(int n)
        {
            // returns maximum value in n-raw
            // NE ZNAM STA CE NAM OVO :)
            /*
            if ((n<0) || (n>=this.mMatrix.Count))
                pukni;
            */
            float max = Math.Abs(this[n][0]);
            for (int j = 1; j < this.mMatrix[n].Count; j++)
                if (max < Math.Abs(this.mMatrix[n][j]))
                    max = Math.Abs(this.mMatrix[n][j]);
            return max;
        }

        public void ExchangeColumns(int k, int l)
        {
            // exchange k and l columns in Matrix
            /*
            if ((k<0) || (k>=this.mMatrix[0].Count) || (l<0) || (l>=this.mMatrix[0]))
            */
            if (k == l)
                return;
            for (int i = 0; i < this.mMatrix.Count; i++)
            {
                float temp = this.mMatrix[i][k];
                this.mMatrix[i][k] = this.mMatrix[i][l];
                this.mMatrix[i][l] = temp;
            }
        }

        public void ExchangeRows(int k, int l)
        {
            // exchange k and l rows in Matrix
            /*
            if ((k<0) || (k>=this.mMatrix.Count) || (l<0) || (l>=this.mMatrix.Count))
            pukni;
            */
            List<float> templist = new List<float>();
            templist = this.mMatrix[l];
            this.mMatrix[l] = this.mMatrix[k];
            this.mMatrix[k] = templist;
        }

        public float Determinant()
        {
            // compute Matirx determinant
            Matrix temp = new Matrix(this.mMatrix);
            int indicator = 1;
            float det = 1;
            for (int i = 0; i < temp.mMatrix.Count; i++)
            {
                int index = temp.IndexOfAbsMaxColumn(i, i);
                det *= temp.mMatrix[i][index];
                if (det == 0) return 0;
                if (0 != index)
                {
                    indicator *= -1;
                    temp.ExchangeColumns(i, index);
                }
                for (int j = i + 1; j < temp.mMatrix.Count; j++)
                {
                    float coef = -temp.mMatrix[j][i] / temp.mMatrix[i][i];
                    // ovo je valjda visak temp.mMatrix[j][i]=0;
                    for (int k = i + 1; k < temp.mMatrix[j].Count; k++)
                    {
                        temp.mMatrix[j][k] += temp.mMatrix[i][k] * coef;
                    }
                }

            }
            return det * indicator;
        }

        public override string ToString()
        {
            // prints Matrix
            string s = "";
            for (int i = 0; i < this.mMatrix.Count; i++)
            {
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    s = s + this.mMatrix[i][j] + "    ";
                s = s + "\n";
            }
            return s;
        }

        public Matrix Transpose()
        {
            // returns transposed Matrix
            // IDEJA JE KAD ODRADIMO mMatrix ovo mzoda postane funkcija te klase!!!
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[j][i];
            return temp;
        }

        public Matrix Diag()
        {
            // returns Matirx 1 x m with values on main diagonal
            Matrix temp = new Matrix(this.mMatrix.Count, 1);
            for (int i = 0; i < this.mMatrix.Count; i++)
            {
                temp.mMatrix[i][0] = this.mMatrix[i][i];
            }
            return temp;
        }

        public Matrix Sum()
        {
            // returns Matix 1 x m with sums on Columns
            Matrix temp = new Matrix(1, this.mMatrix[0].Count, 0);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[0][j] += this.mMatrix[i][j];
            return temp;
        }

        public Matrix ExchangeAll(List<int> rows, List<int> columns)
        {
            // exchanges raws and columns using lists with new classification of index
            // PROVERA SVIH VREDNOSTI IZ LISTI!!, jednog dana bice ovo optimlnije uradjeno :)
            Matrix temp = new Matrix(rows.Count, columns.Count);
            for (int i = 0; i < rows.Count; i++)
                for (int j = 0; j < columns.Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[rows[i]][columns[j]];
            return temp;
        }

        public Matrix Abs()
        {
            // returns Matrix with abs values
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = Math.Abs(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Fliplr()
        {
            // exchange from right to left columns
            // TREBA DA BUDE MNOOOGO OPTIMALNIJA KAD ODRADIMO KLASU mMatrix
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            List<int> templist1 = new List<int>();
            List<int> templist2 = new List<int>();
            for (int i = 0; i < this.mMatrix.Count; i++)
                templist1.Add(i);
            for (int j = this.mMatrix[0].Count - 1; j >= 0; j--)
                templist2.Add(j);
            return this.ExchangeAll(templist1, templist2);
        }

        public Matrix Get(int n, int m)
        {
            // returns Matrix 1 x 1 with value from n raw and m column
            /*
            if((i>=this.matrica.Count) || (j>=this.matrica[0].Count) || (i<0)||(j<0))
                pukni;
            */
            return new Matrix(1, 1, this.mMatrix[n][m]);
        }

        public void Set(int n, int m, Matrix mat)
        {
            for (int j = this.mMatrix[0].Count; j <= m; j++)
            {
                for (int i = 0; i < this.mMatrix.Count; i++)
                    this.mMatrix[i].Add(0);
            }
            for (int i = this.mMatrix.Count; i <= n; i++)
            {
                this.mMatrix.Add(new List<float>());
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    this.mMatrix[i].Add(0);
            }
            this.mMatrix[n][m] = mat.mMatrix[0][0];
        }

        public void Setf(int n, int m, float a)
        {
            for (int j = this.mMatrix[0].Count; j <= m; j++)
            {
                for (int i = 0; i < this.mMatrix.Count; i++)
                    this.mMatrix[i].Add(0);
            }
            for (int i = this.mMatrix.Count; i <= n; i++)
            {
                this.mMatrix.Add(new List<float>());
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    this.mMatrix[i].Add(0);
            }
            this.mMatrix[n][m] = a;
        }

        public Matrix PowDot(float power)
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Pow(this.mMatrix[i][j], power);
            return temp;
        }

        public Matrix Pow(int power)
        {
            /*
            if(this.mMatrix.Count!=this.mMatrix[0].Count)
            pukni;
            */
            Matrix temp = new Matrix(this.mMatrix);
            for (int i = 0; i < power; i++)
            {
                temp = temp.Multiplicate(this);
            }
            return temp;
        }

        public Matrix ConcateRows(Matrix mat)
        {
            // concate 2 Matrix enlarging rows
            /*
            if (this.mMatrix.Count!=mat.mMatrix.Count) 
                pukni;
            */
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count + mat.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[i][j];
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = this.mMatrix[0].Count; j < temp.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = mat.mMatrix[i][j - this.mMatrix[0].Count];
            return temp;
        }

        /// <summary>
        /// concate 2 Matrix enlarging columns
        /// </summary>
        /// <param name="mat">Matrix to Concate</param>
        /// <returns></returns>
        public Matrix ConcateColumns(Matrix mat)
        {
            // concate 2 Matrix enlarging columns
            /*
            if (this.mMatrix[0].Count!=mat.mMatrix[0].Count) 
                pukni;
            */
            Matrix temp = new Matrix(this.mMatrix.Count + mat.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = this.mMatrix[i][j];
            for (int i = this.mMatrix.Count; i < temp.mMatrix.Count; i++)
                for (int j = 0; j < temp.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = mat.mMatrix[i - this.mMatrix.Count][j];
            return temp;
        }

        public float DotProduct(Matrix mat)
        {
            /*if((this.mMatrix[0].Count!=mat.mMatrix[0].Count)||(this.mMatrix.Count!=mat.mMatrix.Count))
            pukni;
            */
            float sum = 0;
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    sum += this.mMatrix[i][j] * mat.mMatrix[i][j];
            return sum;
        }

        public float Norm(float p)
        {
            //p-norm
            /*
            if(p<1)
              pukni;
            */
            float sum = 0;
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    sum += (float)Math.Pow(Math.Abs(this.mMatrix[i][j]), p);
            return (float)Math.Pow(sum, 1 / p);
        }

        public float NormMax()
        {
            //p=inf
            /*
            if(p<1)
              pukni;
            */
            float max = 0;
            float temp;
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    if (max < (temp = (float)Math.Abs(this.mMatrix[i][j]))) max = temp;
            return max;
        }

        public Matrix Sin()
        {
            //ove fje su mozda nepotrebne
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Sin(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Cos()
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Cos(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Tan()
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Tan(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Sinh()
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Sinh(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Cosh()
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Cosh(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix Tanh()
        {
            Matrix temp = new Matrix(this.mMatrix.Count, this.mMatrix[0].Count);
            for (int i = 0; i < this.mMatrix.Count; i++)
                for (int j = 0; j < this.mMatrix[0].Count; j++)
                    temp.mMatrix[i][j] = (float)Math.Tanh(this.mMatrix[i][j]);
            return temp;
        }

        public Matrix BlkDiag(Matrix mat) //prosledjuje se matrica 1xn, dobija se matra nxn
        {
            // Construct block diagonal matrix from input arguments
            /*
            if (mat.mMatrix.Count!=1)
                upozorenje!;
            */
            Matrix temp = new Matrix(mat.mMatrix[0].Count, mat.mMatrix[0].Count);
            for (int i = 0; i < mat.mMatrix[0].Count; i++)
                temp.mMatrix[i][i] = mat.mMatrix[0][i];
            return temp;
        }

        public Matrix ind2sub(Matrix size, Matrix ind)
        {
            Matrix temp = new Matrix(2, ind.mMatrix[0].Count);
            for (int i = 0; i < ind.mMatrix[0].Count; i++)
            {
                temp.mMatrix[0][i] = (int)(ind.mMatrix[0][i] - 1) % size.mMatrix[0][0];
                temp.mMatrix[1][i] = (int)(ind.mMatrix[0][i]) / 2;
            }
            return temp;
        }

        public Matrix LinSpace(Matrix a, Matrix b)
        {
            Matrix temp = new Matrix(1, 100);
            float step = (b.mMatrix[0][0] - a.mMatrix[0][0]) / 99;
            for (int i = 0; i < 98; i++)
            {
                temp.mMatrix[0][i] = a.mMatrix[0][0];
                a.mMatrix[0][0] += step;
            }
            temp.mMatrix[0][99] = b.mMatrix[0][0];
            return temp;
        }

        public Matrix LinSpace(Matrix a, Matrix b, Matrix n)
        {
            Matrix temp = new Matrix(1, (int)n.mMatrix[0][0]);
            int nint = (int)n.mMatrix[0][0];
            float step = (b.mMatrix[0][0] - a.mMatrix[0][0]) / (nint - 1);
            for (int i = 0; i < nint - 2; i++)
            {
                temp.mMatrix[0][i] = a.mMatrix[0][0];
                a.mMatrix[0][0] += step;
            }
            temp.mMatrix[0][nint - 1] = b.mMatrix[0][0];
            return temp;
        }

        public Matrix Inverse()
        {//if (this.mMatrix.Count!=this.mMatrix[0].Count) pukni
            //else if this.Determinant=0 pukni
            Matrix temp = Eye(this.mMatrix.Count);
            Matrix temp1;
            Matrix tempA = new Matrix(this.mMatrix);
            int index;
            for (int j = 0; j < this.mMatrix.Count; j++)
            {
                index = tempA.IndexOfAbsMaxRaw(j, j);
                temp1 = Eye(this.mMatrix.Count);
                temp1.mMatrix[j][j] = 0;
                temp1.mMatrix[index][index] = 0;
                temp1.mMatrix[j][index] = 1;
                temp1.mMatrix[index][j] = 1;
                tempA = temp1.Multiplicate(tempA);
                //Console.WriteLine(tempA);
                temp = temp1.Multiplicate(temp);
                temp1 = Eye(this.mMatrix.Count);
                for (int i = 0; i < this.mMatrix.Count; i++)
                {
                    if (i != j)
                        temp1.mMatrix[i][j] = -tempA.mMatrix[i][j] / tempA.mMatrix[j][j];
                    else temp1.mMatrix[i][j] = 1 / tempA.mMatrix[j][j];

                }
                tempA = temp1.Multiplicate(tempA);
                //Console.WriteLine(tempA);
                temp = temp1.Multiplicate(temp);
            }
            return temp;
        }



    }
}
